/*
 * Copyright (c) 2013 Mohammad Mehdi Saboorian
 *
 * This is part of moor, a wrapper for libarchive
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "ArchiveReader.h"
#include "ScopeExit.h"
#include "LogHelper.h"

#include <archive.h>
#include <archive_entry.h>
#include <stdexcept>
#include <cerrno>
#include <stdlib.h>
#include <stdio.h>

#ifdef _WIN32
#include <io.h>
#include <direct.h>
#else
#include <unistd.h>
#endif


ArchiveReader::ArchiveReader(const std::string& archiveFileName)
    :m_archiveFileName(archiveFileName)
	, m_open(true), m_isFile(true)
{
    struct stat file_stat;
    if (stat(archiveFileName.c_str(), &file_stat) < 0)
    {
        switch (errno)
        {
            case ENOENT:
            {
                m_code = (int32_t)eFileNotFound;
                m_err = "Archive file not found.";
                throw std::runtime_error(m_err);
                break;
            }
            default:
            {
                m_code = (int32_t)eFileNotValid;
                m_err = "Archive file is not valid.";
                throw std::runtime_error(m_err);
                break;
            }
        }
    }

    init();
}

ArchiveReader::ArchiveReader(unsigned char* inBuffer, const size_t size)
	:m_archiveFileName(""), m_open(true), m_isFile(false)
{
	m_inBuffer = std::vector<unsigned char>(inBuffer, inBuffer + size);
    init();
}

ArchiveReader::ArchiveReader(std::vector<unsigned char>&& inBuffer)
    :m_archiveFileName(""), m_inBuffer(std::move(inBuffer))
	, m_open(true), m_isFile(false)
{
    init();
}

void ArchiveReader::init()
{
	m_archive = archive_read_new();

    checkError(archive_read_support_format_all(m_archive), true);
    checkError(archive_read_support_compression_all(m_archive), true);
	if (m_isFile)
	{
		checkError(archive_read_open_file(m_archive, m_archiveFileName.c_str(), 10240), true);
	}
	else
	{
		checkError(archive_read_open_memory(m_archive, &*m_inBuffer.begin(), m_inBuffer.size()), true);
	}
}

ArchiveReader::~ArchiveReader()
{
    close();
}

int32_t copy_data(struct archive *ar, struct archive *aw)
{
    int32_t ret;
    const void *buff;
    size_t size;
    int64_t offset;

    for (;;)
    {
        ret = archive_read_data_block(ar, &buff, &size, &offset);
        if (ret == ARCHIVE_EOF)
            return (ARCHIVE_OK);
        if (ret != ARCHIVE_OK)
            return (ret);
        ret = archive_write_data_block(aw, buff, size, offset);
        if (ret != ARCHIVE_OK)
            return (ret);
    }
}


bool ArchiveReader::writeFileDataTo(const std::string& destination)
{
	try
	{
		int32_t flags;

		/* Select which attributes we want to restore. */
		flags = ARCHIVE_EXTRACT_TIME;
		flags |= ARCHIVE_EXTRACT_PERM;
		flags |= ARCHIVE_EXTRACT_ACL;
		flags |= ARCHIVE_EXTRACT_FFLAGS;

		struct archive* a = archive_write_disk_new();
		archive_write_disk_set_options(a, flags);
		archive_write_disk_set_standard_lookup(a);

        archive_entry_set_pathname(m_entry, (destination + "/" + archive_entry_pathname(m_entry)).c_str());
		checkError(archive_write_header(a, m_entry));

		if (archive_entry_size(m_entry) > 0)
			checkError(copy_data(m_archive, a));

		checkError(archive_write_finish_entry(a));

		ScopeExit se([&a]
		{
			if (a != NULL)
			{
				archive_write_finish_entry(a);
				archive_write_close(a);
				archive_write_free(a);
			}
		});

		return true;
	}
	catch (std::runtime_error& e)
	{
		ERR << "error: " << e.what();
		return false;
	}

	return false;
}
bool ArchiveReader::extractNext(const std::string& destination)
{
    try
    {
        std::string path = destination;
        char buffer[1024] = { 0 };
        if (access(destination.c_str(), 0) != 0)
		{       
            getcwd(buffer, 1024);
            path = std::string(buffer) + "/" + destination;
            if (access(path.c_str(), 0) != 0)
			{
#ifdef _WIN32
                if (mkdir(path.c_str()) != 0)
#else
                if (mkdir(path.c_str(), S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0)
#endif
				{
					m_code = eDirectoryPathError;
					m_err = "Root Path Is Error.";
					return false;
				}
			}
		}

        
        int32_t ret = archive_read_next_header(m_archive, &m_entry);
		if (ret == ARCHIVE_EOF)
		{
			m_code = ARCHIVE_EOF;
			m_err = "ARCHIVE EOF";
			return false;
		}
		else
			checkError(ret);

		////
		if (!writeFileDataTo(path))
			return false;

        return true;
    }
    catch (std::runtime_error& e)
    {
		ERR << "error: " << e.what();
        return false;
    }

    return false;
}

bool ArchiveReader::extractTo(const std::string& destination)
{
    try
    {
        int32_t flags;

        /* Select which attributes we want to restore. */
        flags = ARCHIVE_EXTRACT_TIME;
        flags |= ARCHIVE_EXTRACT_PERM;
        flags |= ARCHIVE_EXTRACT_ACL;
        flags |= ARCHIVE_EXTRACT_FFLAGS;

        struct archive* a = archive_write_disk_new();
        archive_write_disk_set_options(a, flags);
        archive_write_disk_set_standard_lookup(a);

        for (;;)
        {
            
            int32_t ret = archive_read_next_header(m_archive, &m_entry);
            if (ret == ARCHIVE_EOF)
                break;
            else
                checkError(ret);

            archive_entry_set_pathname(m_entry, (destination + "/" + archive_entry_pathname(m_entry)).c_str());
            checkError(archive_write_header(a, m_entry));


            if (archive_entry_size(m_entry) > 0)
                checkError(copy_data(m_archive, a));

            checkError(archive_write_finish_entry(a));

        }

        ScopeExit se([&a]
        {
            if (a != NULL)
            {
                archive_write_finish_entry(a);
                archive_write_close(a);
                archive_write_free(a);
            }
        });

		init();
        return true;
    }
    catch (std::runtime_error& e)
    {
		ERR << "error: " << e.what();
        return false;
    }

    return false;
}

bool ArchiveReader::extractTo(const std::string& fileName, const std::string& destination)
{
	int32_t ret = archive_read_next_header(m_archive, &m_entry);
	while (ret == ARCHIVE_OK)
	{
		std::string entryName = archive_entry_pathname(m_entry);
		if (entryName == fileName)
            return writeFileDataTo(destination);

		ret = archive_read_next_header(m_archive, &m_entry);
	}

	m_err = "file not exist in file list";
	m_code = eFileNotFound;
	return false;
}

std::pair<std::string, std::vector<unsigned char>> ArchiveReader::extractNext()
{
    std::pair<std::string, std::vector<unsigned char>> result = std::make_pair(std::string(""), std::vector<unsigned char>());

    
    int32_t ret = archive_read_next_header(m_archive, &m_entry);
    if (ret == ARCHIVE_EOF)
        return result;
    else
        checkError(ret);

    result.first = archive_entry_pathname(m_entry);
    uint64_t entrySize = archive_entry_size(m_entry);
    if (entrySize > 0)
    {
        int32_t r;
        size_t readIndex = 0;
        result.second.resize(entrySize);
        for (;;)
        {
            r = archive_read_data(m_archive, &result.second[readIndex], result.second.size() - readIndex);
            if (r == 0)
                break;
            if (r < ARCHIVE_OK)
                checkError(r);

            readIndex += r;
            if (readIndex == entrySize)
                break;
        }
    }

    return result;
}

void ArchiveReader::checkError(const int32_t errCode, const bool closeBeforThrow)
{
    std::string error;
    if (errCode != ARCHIVE_OK && errCode != ARCHIVE_WARN)
        error = archive_error_string(m_archive);

    if (closeBeforThrow && errCode == ARCHIVE_FATAL)
        close();

    m_code = errCode;
    m_err = error;

    if (errCode != ARCHIVE_OK && errCode != ARCHIVE_WARN)
        throw std::runtime_error(error);
}

void ArchiveReader::close()
{
    if (m_open)
    {
        if (m_archive != NULL)
        {
            archive_read_close(m_archive);
            archive_read_free(m_archive);
        }

        m_open = false;
    }
}


int32_t ArchiveReader::getLastErrorCode() const
{
    return m_code;
}

std::string ArchiveReader::getLastError() const
{
    return m_err;
}
